home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / gnu / dejagnu.lha / dejagnu-1.0.1 / tcl / tclUnixUtil.c < prev    next >
C/C++ Source or Header  |  1993-02-14  |  28KB  |  1,007 lines

  1. /* 
  2.  * tclUnixUtil.c --
  3.  *
  4.  *    This file contains a collection of utility procedures that
  5.  *    are present in the Tcl's UNIX core but not in the generic
  6.  *    core.  For example, they do file manipulation and process
  7.  *    manipulation.
  8.  *
  9.  *    The Tcl_Fork and Tcl_WaitPids procedures are based on code
  10.  *    contributed by Karl Lehenbauer, Mark Diekhans and Peter
  11.  *    da Silva.
  12.  *
  13.  * Copyright 1991 Regents of the University of California
  14.  * Permission to use, copy, modify, and distribute this
  15.  * software and its documentation for any purpose and without
  16.  * fee is hereby granted, provided that this copyright
  17.  * notice appears in all copies.  The University of California
  18.  * makes no representations about the suitability of this
  19.  * software for any purpose.  It is provided "as is" without
  20.  * express or implied warranty.
  21.  */
  22.  
  23. #include "tclInt.h"
  24. #include "tclUnix.h"
  25.  
  26. /*
  27.  * Data structures of the following type are used by Tcl_Fork and
  28.  * Tcl_WaitPids to keep track of child processes.
  29.  */
  30.  
  31. typedef struct {
  32.     int pid;            /* Process id of child. */
  33.     WAIT_STATUS_TYPE status;    /* Status returned when child exited or
  34.                  * suspended. */
  35.     int flags;            /* Various flag bits;  see below for
  36.                  * definitions. */
  37. } WaitInfo;
  38.  
  39. /*
  40.  * Flag bits in WaitInfo structures:
  41.  *
  42.  * WI_READY -            Non-zero means process has exited or
  43.  *                suspended since it was forked or last
  44.  *                returned by Tcl_WaitPids.
  45.  * WI_DETACHED -        Non-zero means no-one cares about the
  46.  *                process anymore.  Ignore it until it
  47.  *                exits, then forget about it.
  48.  */
  49.  
  50. #define WI_READY    1
  51. #define WI_DETACHED    2
  52.  
  53. static WaitInfo *waitTable = NULL;
  54. static int waitTableSize = 0;    /* Total number of entries available in
  55.                  * waitTable. */
  56. static int waitTableUsed = 0;    /* Number of entries in waitTable that
  57.                  * are actually in use right now.  Active
  58.                  * entries are always at the beginning
  59.                  * of the table. */
  60. #define WAIT_TABLE_GROW_BY 4
  61.  
  62. /*
  63.  *----------------------------------------------------------------------
  64.  *
  65.  * Tcl_EvalFile --
  66.  *
  67.  *    Read in a file and process the entire file as one gigantic
  68.  *    Tcl command.
  69.  *
  70.  * Results:
  71.  *    A standard Tcl result, which is either the result of executing
  72.  *    the file or an error indicating why the file couldn't be read.
  73.  *
  74.  * Side effects:
  75.  *    Depends on the commands in the file.
  76.  *
  77.  *----------------------------------------------------------------------
  78.  */
  79.  
  80. int
  81. Tcl_EvalFile(interp, fileName)
  82.     Tcl_Interp *interp;        /* Interpreter in which to process file. */
  83.     char *fileName;        /* Name of file to process.  Tilde-substitution
  84.                  * will be performed on this name. */
  85. {
  86.     int fileId, result;
  87.     struct stat statBuf;
  88.     char *cmdBuffer, *end, *oldScriptFile;
  89.     Interp *iPtr = (Interp *) interp;
  90.  
  91.     oldScriptFile = iPtr->scriptFile;
  92.     iPtr->scriptFile = fileName;
  93.     fileName = Tcl_TildeSubst(interp, fileName);
  94.     if (fileName == NULL) {
  95.     goto error;
  96.     }
  97.     fileId = open(fileName, O_RDONLY, 0);
  98.     if (fileId < 0) {
  99.     Tcl_AppendResult(interp, "couldn't read file \"", fileName,
  100.         "\": ", Tcl_UnixError(interp), (char *) NULL);
  101.     goto error;
  102.     }
  103.     if (fstat(fileId, &statBuf) == -1) {
  104.     Tcl_AppendResult(interp, "couldn't stat file \"", fileName,
  105.         "\": ", Tcl_UnixError(interp), (char *) NULL);
  106.     close(fileId);
  107.     goto error;
  108.     }
  109.     cmdBuffer = (char *) ckalloc((unsigned) statBuf.st_size+1);
  110.     if (read(fileId, cmdBuffer, (int) statBuf.st_size) != statBuf.st_size) {
  111.     Tcl_AppendResult(interp, "error in reading file \"", fileName,
  112.         "\": ", Tcl_UnixError(interp), (char *) NULL);
  113.     close(fileId);
  114.     ckfree(cmdBuffer);
  115.     goto error;
  116.     }
  117.     if (close(fileId) != 0) {
  118.     Tcl_AppendResult(interp, "error closing file \"", fileName,
  119.         "\": ", Tcl_UnixError(interp), (char *) NULL);
  120.     ckfree(cmdBuffer);
  121.     goto error;
  122.     }
  123.     cmdBuffer[statBuf.st_size] = 0;
  124.     result = Tcl_Eval(interp, cmdBuffer, 0, &end);
  125.     if (result == TCL_RETURN) {
  126.     result = TCL_OK;
  127.     }
  128.     if (result == TCL_ERROR) {
  129.     char msg[200];
  130.  
  131.     /*
  132.      * Record information telling where the error occurred.
  133.      */
  134.  
  135.     sprintf(msg, "\n    (file \"%.150s\" line %d)", fileName,
  136.         interp->errorLine);
  137.     Tcl_AddErrorInfo(interp, msg);
  138.     }
  139.     ckfree(cmdBuffer);
  140.     iPtr->scriptFile = oldScriptFile;
  141.     return result;
  142.  
  143.     error:
  144.     iPtr->scriptFile = oldScriptFile;
  145.     return TCL_ERROR;
  146. }
  147.  
  148. /*
  149.  *----------------------------------------------------------------------
  150.  *
  151.  * Tcl_Fork --
  152.  *
  153.  *    Create a new process using the vfork system call, and keep
  154.  *    track of it for "safe" waiting with Tcl_WaitPids.
  155.  *
  156.  * Results:
  157.  *    The return value is the value returned by the vfork system
  158.  *    call (0 means child, > 0 means parent (value is child id),
  159.  *    < 0 means error).
  160.  *
  161.  * Side effects:
  162.  *    A new process is created, and an entry is added to an internal
  163.  *    table of child processes if the process is created successfully.
  164.  *
  165.  *----------------------------------------------------------------------
  166.  */
  167.  
  168. int
  169. Tcl_Fork()
  170. {
  171.     WaitInfo *waitPtr;
  172.     pid_t pid;
  173.  
  174.     /*
  175.      * Disable SIGPIPE signals:  if they were allowed, this process
  176.      * might go away unexpectedly if children misbehave.  This code
  177.      * can potentially interfere with other application code that
  178.      * expects to handle SIGPIPEs;  what's really needed is an
  179.      * arbiter for signals to allow them to be "shared".
  180.      */
  181.  
  182.     if (waitTable == NULL) {
  183.     (void) signal(SIGPIPE, SIG_IGN);
  184.     }
  185.  
  186.     /*
  187.      * Enlarge the wait table if there isn't enough space for a new
  188.      * entry.
  189.      */
  190.  
  191.     if (waitTableUsed == waitTableSize) {
  192.     int newSize;
  193.     WaitInfo *newWaitTable;
  194.  
  195.     newSize = waitTableSize + WAIT_TABLE_GROW_BY;
  196.     newWaitTable = (WaitInfo *) ckalloc((unsigned)
  197.         (newSize * sizeof(WaitInfo)));
  198.     memcpy((VOID *) newWaitTable, (VOID *) waitTable,
  199.         (waitTableSize * sizeof(WaitInfo)));
  200.     if (waitTable != NULL) {
  201.         ckfree((char *) waitTable);
  202.     }
  203.     waitTable = newWaitTable;
  204.     waitTableSize = newSize;
  205.     }
  206.  
  207.     /*
  208.      * Make a new process and enter it into the table if the fork
  209.      * is successful.
  210.      */
  211.  
  212.     waitPtr = &waitTable[waitTableUsed];
  213.     pid = fork();
  214.     if (pid > 0) {
  215.     waitPtr->pid = pid;
  216.     waitPtr->flags = 0;
  217.     waitTableUsed++;
  218.     }
  219.     return pid;
  220. }
  221.  
  222. /*
  223.  *----------------------------------------------------------------------
  224.  *
  225.  * Tcl_WaitPids --
  226.  *
  227.  *    This procedure is used to wait for one or more processes created
  228.  *    by Tcl_Fork to exit or suspend.  It records information about
  229.  *    all processes that exit or suspend, even those not waited for,
  230.  *    so that later waits for them will be able to get the status
  231.  *    information.
  232.  *
  233.  * Results:
  234.  *    -1 is returned if there is an error in the wait kernel call.
  235.  *    Otherwise the pid of an exited/suspended process from *pidPtr
  236.  *    is returned and *statusPtr is set to the status value returned
  237.  *    by the wait kernel call.
  238.  *
  239.  * Side effects:
  240.  *    Doesn't return until one of the pids at *pidPtr exits or suspends.
  241.  *
  242.  *----------------------------------------------------------------------
  243.  */
  244.  
  245. int
  246. Tcl_WaitPids(numPids, pidPtr, statusPtr)
  247.     int numPids;        /* Number of pids to wait on:  gives size
  248.                  * of array pointed to by pidPtr. */
  249.     int *pidPtr;        /* Pids to wait on:  return when one of
  250.                  * these processes exits or suspends. */
  251.     int *statusPtr;        /* Wait status is returned here. */
  252. {
  253.     int i, count, pid;
  254.     register WaitInfo *waitPtr;
  255.     int anyProcesses;
  256.     WAIT_STATUS_TYPE status;
  257.  
  258.     while (1) {
  259.     /*
  260.      * Scan the table of child processes to see if one of the
  261.      * specified children has already exited or suspended.  If so,
  262.      * remove it from the table and return its status.
  263.      */
  264.  
  265.     anyProcesses = 0;
  266.     for (waitPtr = waitTable, count = waitTableUsed;
  267.         count > 0; waitPtr++, count--) {
  268.         for (i = 0; i < numPids; i++) {
  269.         if (pidPtr[i] != waitPtr->pid) {
  270.             continue;
  271.         }
  272.         anyProcesses = 1;
  273.         if (waitPtr->flags & WI_READY) {
  274.             *statusPtr = *((int *) &waitPtr->status);
  275.             pid = waitPtr->pid;
  276.             if (WIFEXITED(waitPtr->status)
  277.                 || WIFSIGNALED(waitPtr->status)) {
  278.             *waitPtr = waitTable[waitTableUsed-1];
  279.             waitTableUsed--;
  280.             } else {
  281.             waitPtr->flags &= ~WI_READY;
  282.             }
  283.             return pid;
  284.         }
  285.         }
  286.     }
  287.  
  288.     /*
  289.      * Make sure that the caller at least specified one valid
  290.      * process to wait for.
  291.      */
  292.  
  293.     if (!anyProcesses) {
  294.         errno = ECHILD;
  295.         return -1;
  296.     }
  297.  
  298.     /*
  299.      * Wait for a process to exit or suspend, then update its
  300.      * entry in the table and go back to the beginning of the
  301.      * loop to see if it's one of the desired processes.
  302.      */
  303.  
  304.     pid = wait(&status);
  305.     if (pid < 0) {
  306.         return pid;
  307.     }
  308.     for (waitPtr = waitTable, count = waitTableUsed; ;
  309.         waitPtr++, count--) {
  310.         if (count == 0) {
  311.         break;            /* Ignore unknown processes. */
  312.         }
  313.         if (pid != waitPtr->pid) {
  314.         continue;
  315.         }
  316.  
  317.         /*
  318.          * If the process has been detached, then ignore anything
  319.          * other than an exit, and drop the entry on exit.
  320.          */
  321.  
  322.         if (waitPtr->flags & WI_DETACHED) {
  323.         if (WIFEXITED(status) || WIFSIGNALED(status)) {
  324.             *waitPtr = waitTable[waitTableUsed-1];
  325.             waitTableUsed--;
  326.         }
  327.         } else {
  328.         waitPtr->status = status;
  329.         waitPtr->flags |= WI_READY;
  330.         }
  331.         break;
  332.     }
  333.     }
  334. }
  335.  
  336. /*
  337.  *----------------------------------------------------------------------
  338.  *
  339.  * Tcl_DetachPids --
  340.  *
  341.  *    This procedure is called to indicate that one or more child
  342.  *    processes have been placed in background and are no longer
  343.  *    cared about.  They should be ignored in future calls to
  344.  *    Tcl_WaitPids.
  345.  *
  346.  * Results:
  347.  *    None.
  348.  *
  349.  * Side effects:
  350.  *    None.
  351.  *
  352.  *----------------------------------------------------------------------
  353.  */
  354.  
  355. void
  356. Tcl_DetachPids(numPids, pidPtr)
  357.     int numPids;        /* Number of pids to detach:  gives size
  358.                  * of array pointed to by pidPtr. */
  359.     int *pidPtr;        /* Array of pids to detach:  must have
  360.                  * been created by Tcl_Fork. */
  361. {
  362.     register WaitInfo *waitPtr;
  363.     int i, count, pid;
  364.  
  365.     for (i = 0; i < numPids; i++) {
  366.     pid = pidPtr[i];
  367.     for (waitPtr = waitTable, count = waitTableUsed;
  368.         count > 0; waitPtr++, count--) {
  369.         if (pid != waitPtr->pid) {
  370.         continue;
  371.         }
  372.  
  373.         /*
  374.          * If the process has already exited then destroy its
  375.          * table entry now.
  376.          */
  377.  
  378.         if ((waitPtr->flags & WI_READY) && (WIFEXITED(waitPtr->status)
  379.             || WIFSIGNALED(waitPtr->status))) {
  380.         *waitPtr = waitTable[waitTableUsed-1];
  381.         waitTableUsed--;
  382.         } else {
  383.         waitPtr->flags |= WI_DETACHED;
  384.         }
  385.         goto nextPid;
  386.     }
  387.     panic("Tcl_Detach couldn't find process");
  388.  
  389.     nextPid:
  390.     continue;
  391.     }
  392. }
  393.  
  394. /*
  395.  *----------------------------------------------------------------------
  396.  *
  397.  * Tcl_CreatePipeline --
  398.  *
  399.  *    Given an argc/argv array, instantiate a pipeline of processes
  400.  *    as described by the argv.
  401.  *
  402.  * Results:
  403.  *    The return value is a count of the number of new processes
  404.  *    created, or -1 if an error occurred while creating the pipeline.
  405.  *    *pidArrayPtr is filled in with the address of a dynamically
  406.  *    allocated array giving the ids of all of the processes.  It
  407.  *    is up to the caller to free this array when it isn't needed
  408.  *    anymore.  If inPipePtr is non-NULL, *inPipePtr is filled in
  409.  *    with the file id for the input pipe for the pipeline (if any):
  410.  *    the caller must eventually close this file.  If outPipePtr
  411.  *    isn't NULL, then *outPipePtr is filled in with the file id
  412.  *    for the output pipe from the pipeline:  the caller must close
  413.  *    this file.  If errFilePtr isn't NULL, then *errFilePtr is filled
  414.  *    with a file id that may be used to read error output after the
  415.  *    pipeline completes.
  416.  *
  417.  * Side effects:
  418.  *    Processes and pipes are created.
  419.  *
  420.  *----------------------------------------------------------------------
  421.  */
  422.  
  423. int
  424. Tcl_CreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,
  425.     outPipePtr, errFilePtr)
  426.     Tcl_Interp *interp;        /* Interpreter to use for error reporting. */
  427.     int argc;            /* Number of entries in argv. */
  428.     char **argv;        /* Array of strings describing commands in
  429.                  * pipeline plus I/O redirection with <,
  430.                  * <<, and >.  Argv[argc] must be NULL. */
  431.     int **pidArrayPtr;        /* Word at *pidArrayPtr gets filled in with
  432.                  * address of array of pids for processes
  433.                  * in pipeline (first pid is first process
  434.                  * in pipeline). */
  435.     int *inPipePtr;        /* If non-NULL, input to the pipeline comes
  436.                  * from a pipe (unless overridden by
  437.                  * redirection in the command).  The file
  438.                  * id with which to write to this pipe is
  439.                  * stored at *inPipePtr.  -1 means command
  440.                  * specified its own input source. */
  441.     int *outPipePtr;        /* If non-NULL, output to the pipeline goes
  442.                  * to a pipe, unless overriden by redirection
  443.                  * in the command.  The file id with which to
  444.                  * read frome this pipe is stored at
  445.                  * *outPipePtr.  -1 means command specified
  446.                  * its own output sink. */
  447.     int *errFilePtr;        /* If non-NULL, all stderr output from the
  448.                  * pipeline will go to a temporary file
  449.                  * created here, and a descriptor to read
  450.                  * the file will be left at *errFilePtr.
  451.                  * The file will be removed already, so
  452.                  * closing this descriptor will be the end
  453.                  * of the file.  If this is NULL, then
  454.                  * all stderr output goes to our stderr. */
  455. {
  456.     int *pidPtr = NULL;        /* Points to malloc-ed array holding all
  457.                  * the pids of child processes. */
  458.     int numPids = 0;        /* Actual number of processes that exist
  459.                  * at *pidPtr right now. */
  460.     int cmdCount;        /* Count of number of distinct commands
  461.                  * found in argc/argv. */
  462.     char *input = NULL;        /* Describes input for pipeline, depending
  463.                  * on "inputFile".  NULL means take input
  464.                  * from stdin/pipe. */
  465.     int inputFile = 0;        /* Non-zero means input is name of input
  466.                  * file.  Zero means input holds actual
  467.                  * text to be input to command. */
  468.     char *output = NULL;    /* Holds name of output file to pipe to,
  469.                  * or NULL if output goes to stdout/pipe. */
  470.     int inputId = -1;        /* Readable file id input to current command in
  471.                  * pipeline (could be file or pipe).  -1
  472.                  * means use stdin. */
  473.     int outputId = -1;        /* Writable file id for output from current
  474.                  * command in pipeline (could be file or pipe).
  475.                  * -1 means use stdout. */
  476.     int errorId = -1;        /* Writable file id for all standard error
  477.                  * output from all commands in pipeline.  -1
  478.                  * means use stderr. */
  479.     int lastOutputId = -1;    /* Write file id for output from last command
  480.                  * in pipeline (could be file or pipe).
  481.                  * -1 means use stdout. */
  482.     int pipeIds[2];        /* File ids for pipe that's being created. */
  483.     int firstArg, lastArg;    /* Indexes of first and last arguments in
  484.                  * current command. */
  485.     int lastBar;
  486.     char *execName;
  487.     int i, j, pid;
  488.  
  489.     if (inPipePtr != NULL) {
  490.     *inPipePtr = -1;
  491.     }
  492.     if (outPipePtr != NULL) {
  493.     *outPipePtr = -1;
  494.     }
  495.     if (errFilePtr != NULL) {
  496.     *errFilePtr = -1;
  497.     }
  498.     pipeIds[0] = pipeIds[1] = -1;
  499.  
  500.     /*
  501.      * First, scan through all the arguments to figure out the structure
  502.      * of the pipeline.  Count the number of distinct processes (it's the
  503.      * number of "|" arguments).  If there are "<", "<<", or ">" arguments
  504.      * then make note of input and output redirection and remove these
  505.      * arguments and the arguments that follow them.
  506.      */
  507.  
  508.     cmdCount = 1;
  509.     lastBar = -1;
  510.     for (i = 0; i < argc; i++) {
  511.     if ((argv[i][0] == '|') && ((argv[i][1] == 0))) {
  512.         if ((i == (lastBar+1)) || (i == (argc-1))) {
  513.         interp->result = "illegal use of | in command";
  514.         return -1;
  515.         }
  516.         lastBar = i;
  517.         cmdCount++;
  518.         continue;
  519.     } else if (argv[i][0] == '<') {
  520.         if (argv[i][1] == 0) {
  521.         input = argv[i+1];
  522.         inputFile = 1;
  523.         } else if ((argv[i][1] == '<') && (argv[i][2] == 0)) {
  524.         input = argv[i+1];
  525.         inputFile = 0;
  526.         } else {
  527.         continue;
  528.         }
  529.     } else if ((argv[i][0] == '>') && (argv[i][1] == 0)) {
  530.         output = argv[i+1];
  531.     } else {
  532.         continue;
  533.     }
  534.     if (i >= (argc-1)) {
  535.         Tcl_AppendResult(interp, "can't specify \"", argv[i],
  536.             "\" as last word in command", (char *) NULL);
  537.         return -1;
  538.     }
  539.     for (j = i+2; j < argc; j++) {
  540.         argv[j-2] = argv[j];
  541.     }
  542.     argc -= 2;
  543.     i--;            /* Process new arg from same position. */
  544.     }
  545.     if (argc == 0) {
  546.     interp->result =  "didn't specify command to execute";
  547.     return -1;
  548.     }
  549.  
  550.     /*
  551.      * Set up the redirected input source for the pipeline, if
  552.      * so requested.
  553.      */
  554.  
  555.     if (input != NULL) {
  556.     if (!inputFile) {
  557.         /*
  558.          * Immediate data in command.  Create temporary file and
  559.          * put data into file.
  560.          */
  561.  
  562. #        define TMP_STDIN_NAME "/tmp/tcl.in.XXXXXX"
  563.         char inName[sizeof(TMP_STDIN_NAME) + 1];
  564.         int length;
  565.  
  566.         strcpy(inName, TMP_STDIN_NAME);
  567.         mktemp(inName);
  568.         inputId = open(inName, O_RDWR|O_CREAT|O_TRUNC, 0600);
  569.         if (inputId < 0) {
  570.         Tcl_AppendResult(interp,
  571.             "couldn't create input file for command: ",
  572.             Tcl_UnixError(interp), (char *) NULL);
  573.         goto error;
  574.         }
  575.         length = strlen(input);
  576.         if (write(inputId, input, length) != length) {
  577.         Tcl_AppendResult(interp,
  578.             "couldn't write file input for command: ",
  579.             Tcl_UnixError(interp), (char *) NULL);
  580.         goto error;
  581.         }
  582.         if ((lseek(inputId, 0L, 0) == -1) || (unlink(inName) == -1)) {
  583.         Tcl_AppendResult(interp,
  584.             "couldn't reset or remove input file for command: ",
  585.             Tcl_UnixError(interp), (char *) NULL);
  586.         goto error;
  587.         }
  588.     } else {
  589.         /*
  590.          * File redirection.  Just open the file.
  591.          */
  592.  
  593.         inputId = open(input, O_RDONLY, 0);
  594.         if (inputId < 0) {
  595.         Tcl_AppendResult(interp,
  596.             "couldn't read file \"", input, "\": ",
  597.             Tcl_UnixError(interp), (char *) NULL);
  598.         goto error;
  599.         }
  600.     }
  601.     } else if (inPipePtr != NULL) {
  602.     if (pipe(pipeIds) != 0) {
  603.         Tcl_AppendResult(interp,
  604.             "couldn't create input pipe for command: ",
  605.             Tcl_UnixError(interp), (char *) NULL);
  606.         goto error;
  607.     }
  608.     inputId = pipeIds[0];
  609.     *inPipePtr = pipeIds[1];
  610.     pipeIds[0] = pipeIds[1] = -1;
  611.     }
  612.  
  613.     /*
  614.      * Set up the redirected output sink for the pipeline from one
  615.      * of two places, if requested.
  616.      */
  617.  
  618.     if (output != NULL) {
  619.     /*
  620.      * Output is to go to a file.
  621.      */
  622.  
  623.     lastOutputId = open(output, O_WRONLY|O_CREAT|O_TRUNC, 0666);
  624.     if (lastOutputId < 0) {
  625.         Tcl_AppendResult(interp,
  626.             "couldn't write file \"", output, "\": ",
  627.             Tcl_UnixError(interp), (char *) NULL);
  628.         goto error;
  629.     }
  630.     } else if (outPipePtr != NULL) {
  631.     /*
  632.      * Output is to go to a pipe.
  633.      */
  634.  
  635.     if (pipe(pipeIds) != 0) {
  636.         Tcl_AppendResult(interp,
  637.             "couldn't create output pipe: ",
  638.             Tcl_UnixError(interp), (char *) NULL);
  639.         goto error;
  640.     }
  641.     lastOutputId = pipeIds[1];
  642.     *outPipePtr = pipeIds[0];
  643.     pipeIds[0] = pipeIds[1] = -1;
  644.     }
  645.  
  646.     /*
  647.      * Set up the standard error output sink for the pipeline, if
  648.      * requested.  Use a temporary file which is opened, then deleted.
  649.      * Could potentially just use pipe, but if it filled up it could
  650.      * cause the pipeline to deadlock:  we'd be waiting for processes
  651.      * to complete before reading stderr, and processes couldn't complete
  652.      * because stderr was backed up.
  653.      */
  654.  
  655.     if (errFilePtr != NULL) {
  656. #    define TMP_STDERR_NAME "/tmp/tcl.err.XXXXXX"
  657.     char errName[sizeof(TMP_STDERR_NAME) + 1];
  658.  
  659.     strcpy(errName, TMP_STDERR_NAME);
  660.     mktemp(errName);
  661.     errorId = open(errName, O_WRONLY|O_CREAT|O_TRUNC, 0600);
  662.     if (errorId < 0) {
  663.         errFileError:
  664.         Tcl_AppendResult(interp,
  665.             "couldn't create error file for command: ",
  666.             Tcl_UnixError(interp), (char *) NULL);
  667.         goto error;
  668.     }
  669.     *errFilePtr = open(errName, O_RDONLY, 0);
  670.     if (*errFilePtr < 0) {
  671.         goto errFileError;
  672.     }
  673.     if (unlink(errName) == -1) {
  674.         Tcl_AppendResult(interp,
  675.             "couldn't remove error file for command: ",
  676.             Tcl_UnixError(interp), (char *) NULL);
  677.         goto error;
  678.     }
  679.     }
  680.  
  681.     /*
  682.      * Scan through the argc array, forking off a process for each
  683.      * group of arguments between "|" arguments.
  684.      */
  685.  
  686.     pidPtr = (int *) ckalloc((unsigned) (cmdCount * sizeof(int)));
  687.     for (i = 0; i < numPids; i++) {
  688.     pidPtr[i] = -1;
  689.     }
  690.     for (firstArg = 0; firstArg < argc; numPids++, firstArg = lastArg+1) {
  691.     for (lastArg = firstArg; lastArg < argc; lastArg++) {
  692.         if ((argv[lastArg][0] == '|') && (argv[lastArg][1] == 0)) {
  693.         break;
  694.         }
  695.     }
  696.     argv[lastArg] = NULL;
  697.     if (lastArg == argc) {
  698.         outputId = lastOutputId;
  699.     } else {
  700.         if (pipe(pipeIds) != 0) {
  701.         Tcl_AppendResult(interp, "couldn't create pipe: ",
  702.             Tcl_UnixError(interp), (char *) NULL);
  703.         goto error;
  704.         }
  705.         outputId = pipeIds[1];
  706.     }
  707.     execName = Tcl_TildeSubst(interp, argv[firstArg]);
  708.     pid = Tcl_Fork();
  709.     if (pid == -1) {
  710.         Tcl_AppendResult(interp, "couldn't fork child process: ",
  711.             Tcl_UnixError(interp), (char *) NULL);
  712.         goto error;
  713.     }
  714.     if (pid == 0) {
  715.         char errSpace[200];
  716.  
  717.         if (((inputId != -1) && (dup2(inputId, 0) == -1))
  718.             || ((outputId != -1) && (dup2(outputId, 1) == -1))
  719.             || ((errorId != -1) && (dup2(errorId, 2) == -1))) {
  720.         char *err;
  721.         err = "forked process couldn't set up input/output\n";
  722.         write(errorId < 0 ? 2 : errorId, err, strlen(err));
  723.         _exit(1);
  724.         }
  725.         for (i = 3; (i <= outputId) || (i <= inputId) || (i <= errorId);
  726.             i++) {
  727.         close(i);
  728.         }
  729.         execvp(execName, &argv[firstArg]);
  730.         sprintf(errSpace, "couldn't find \"%.150s\" to execute\n",
  731.             argv[firstArg]);
  732.         write(2, errSpace, strlen(errSpace));
  733.         _exit(1);
  734.     } else {
  735.         pidPtr[numPids] = pid;
  736.     }
  737.  
  738.     /*
  739.      * Close off our copies of file descriptors that were set up for
  740.      * this child, then set up the input for the next child.
  741.      */
  742.  
  743.     if (inputId != -1) {
  744.         close(inputId);
  745.     }
  746.     if (outputId != -1) {
  747.         close(outputId);
  748.     }
  749.     inputId = pipeIds[0];
  750.     pipeIds[0] = pipeIds[1] = -1;
  751.     }
  752.     *pidArrayPtr = pidPtr;
  753.  
  754.     /*
  755.      * All done.  Cleanup open files lying around and then return.
  756.      */
  757.  
  758. cleanup:
  759.     if (inputId != -1) {
  760.     close(inputId);
  761.     }
  762.     if (lastOutputId != -1) {
  763.     close(lastOutputId);
  764.     }
  765.     if (errorId != -1) {
  766.     close(errorId);
  767.     }
  768.     return numPids;
  769.  
  770.     /*
  771.      * An error occurred.  There could have been extra files open, such
  772.      * as pipes between children.  Clean them all up.  Detach any child
  773.      * processes that have been created.
  774.      */
  775.  
  776.     error:
  777.     if ((inPipePtr != NULL) && (*inPipePtr != -1)) {
  778.     close(*inPipePtr);
  779.     *inPipePtr = -1;
  780.     }
  781.     if ((outPipePtr != NULL) && (*outPipePtr != -1)) {
  782.     close(*outPipePtr);
  783.     *outPipePtr = -1;
  784.     }
  785.     if ((errFilePtr != NULL) && (*errFilePtr != -1)) {
  786.     close(*errFilePtr);
  787.     *errFilePtr = -1;
  788.     }
  789.     if (pipeIds[0] != -1) {
  790.     close(pipeIds[0]);
  791.     }
  792.     if (pipeIds[1] != -1) {
  793.     close(pipeIds[1]);
  794.     }
  795.     if (pidPtr != NULL) {
  796.     for (i = 0; i < numPids; i++) {
  797.         if (pidPtr[i] != -1) {
  798.         Tcl_DetachPids(1, &pidPtr[i]);
  799.         }
  800.     }
  801.     ckfree((char *) pidPtr);
  802.     }
  803.     numPids = -1;
  804.     goto cleanup;
  805. }
  806.  
  807. /*
  808.  *----------------------------------------------------------------------
  809.  *
  810.  * Tcl_UnixError --
  811.  *
  812.  *    This procedure is typically called after UNIX kernel calls
  813.  *    return errors.  It stores machine-readable information about
  814.  *    the error in $errorCode returns an information string for
  815.  *    the caller's use.
  816.  *
  817.  * Results:
  818.  *    The return value is a human-readable string describing the
  819.  *    error, as returned by strerror.
  820.  *
  821.  * Side effects:
  822.  *    The global variable $errorCode is reset.
  823.  *
  824.  *----------------------------------------------------------------------
  825.  */
  826.  
  827. char *
  828. Tcl_UnixError(interp)
  829.     Tcl_Interp *interp;        /* Interpreter whose $errorCode variable
  830.                  * is to be changed. */
  831. {
  832.     char *id, *msg;
  833.  
  834.     id = Tcl_ErrnoId();
  835.     msg = strerror(errno);
  836.     Tcl_SetErrorCode(interp, "UNIX", id, msg, (char *) NULL);
  837.     return msg;
  838. }
  839.  
  840. /*
  841.  *----------------------------------------------------------------------
  842.  *
  843.  * TclMakeFileTable --
  844.  *
  845.  *    Create or enlarge the file table for the interpreter, so that
  846.  *    there is room for a given index.
  847.  *
  848.  * Results:
  849.  *    None.
  850.  *
  851.  * Side effects:
  852.  *    The file table for iPtr will be created if it doesn't exist
  853.  *    (and entries will be added for stdin, stdout, and stderr).
  854.  *    If it already exists, then it will be grown if necessary.
  855.  *
  856.  *----------------------------------------------------------------------
  857.  */
  858.  
  859. void
  860. TclMakeFileTable(iPtr, index)
  861.     Interp *iPtr;        /* Interpreter whose table of files is
  862.                  * to be manipulated. */
  863.     int index;            /* Make sure table is large enough to
  864.                  * hold at least this index. */
  865. {
  866.     /*
  867.      * If the table doesn't even exist, then create it and initialize
  868.      * entries for standard files.
  869.      */
  870.  
  871.     if (iPtr->numFiles == 0) {
  872.     OpenFile *filePtr;
  873.     int i;
  874.  
  875.     if (index < 2) {
  876.         iPtr->numFiles = 3;
  877.     } else {
  878.         iPtr->numFiles = index+1;
  879.     }
  880.     iPtr->filePtrArray = (OpenFile **) ckalloc((unsigned)
  881.         ((iPtr->numFiles)*sizeof(OpenFile *)));
  882.     for (i = iPtr->numFiles-1; i >= 0; i--) {
  883.         iPtr->filePtrArray[i] = NULL;
  884.     }
  885.  
  886.     filePtr = (OpenFile *) ckalloc(sizeof(OpenFile));
  887.     filePtr->f = stdin;
  888.     filePtr->f2 = NULL;
  889.     filePtr->readable = 1;
  890.     filePtr->writable = 0;
  891.     filePtr->numPids = 0;
  892.     filePtr->pidPtr = NULL;
  893.     filePtr->errorId = -1;
  894.     iPtr->filePtrArray[0] = filePtr;
  895.  
  896.     filePtr = (OpenFile *) ckalloc(sizeof(OpenFile));
  897.     filePtr->f = stdout;
  898.     filePtr->f2 = NULL;
  899.     filePtr->readable = 0;
  900.     filePtr->writable = 1;
  901.     filePtr->numPids = 0;
  902.     filePtr->pidPtr = NULL;
  903.     filePtr->errorId = -1;
  904.     iPtr->filePtrArray[1] = filePtr;
  905.  
  906.     filePtr = (OpenFile *) ckalloc(sizeof(OpenFile));
  907.     filePtr->f = stderr;
  908.     filePtr->f2 = NULL;
  909.     filePtr->readable = 0;
  910.     filePtr->writable = 1;
  911.     filePtr->numPids = 0;
  912.     filePtr->pidPtr = NULL;
  913.     filePtr->errorId = -1;
  914.     iPtr->filePtrArray[2] = filePtr;
  915.     } else if (index >= iPtr->numFiles) {
  916.     int newSize;
  917.     OpenFile **newPtrArray;
  918.     int i;
  919.  
  920.     newSize = index+1;
  921.     newPtrArray = (OpenFile **) ckalloc((unsigned)
  922.         ((newSize)*sizeof(OpenFile *)));
  923.     memcpy((VOID *) newPtrArray, (VOID *) iPtr->filePtrArray,
  924.         iPtr->numFiles*sizeof(OpenFile *));
  925.     for (i = iPtr->numFiles; i < newSize; i++) {
  926.         newPtrArray[i] = NULL;
  927.     }
  928.     ckfree((char *) iPtr->filePtrArray);
  929.     iPtr->numFiles = newSize;
  930.     iPtr->filePtrArray = newPtrArray;
  931.     }
  932. }
  933.  
  934. /*
  935.  *----------------------------------------------------------------------
  936.  *
  937.  * TclGetOpenFile --
  938.  *
  939.  *    Given a string identifier for an open file, find the corresponding
  940.  *    open file structure, if there is one.
  941.  *
  942.  * Results:
  943.  *    A standard Tcl return value.  If the open file is successfully
  944.  *    located, *filePtrPtr is modified to point to its structure.
  945.  *    If TCL_ERROR is returned then interp->result contains an error
  946.  *    message.
  947.  *
  948.  * Side effects:
  949.  *    None.
  950.  *
  951.  *----------------------------------------------------------------------
  952.  */
  953.  
  954. int
  955. TclGetOpenFile(interp, string, filePtrPtr)
  956.     Tcl_Interp *interp;        /* Interpreter in which to find file. */
  957.     char *string;        /* String that identifies file. */
  958.     OpenFile **filePtrPtr;    /* Address of word in which to store pointer
  959.                  * to structure about open file. */
  960. {
  961.     int fd = 0;            /* Initial value needed only to stop compiler
  962.                  * warnings. */
  963.     Interp *iPtr = (Interp *) interp;
  964.  
  965.     if ((string[0] == 'f') && (string[1] == 'i') && (string[2] == 'l')
  966.         & (string[3] == 'e')) {
  967.     char *end;
  968.  
  969.     fd = strtoul(string+4, &end, 10);
  970.     if ((end == string+4) || (*end != 0)) {
  971.         goto badId;
  972.     }
  973.     } else if ((string[0] == 's') && (string[1] == 't')
  974.         && (string[2] == 'd')) {
  975.     if (strcmp(string+3, "in") == 0) {
  976.         fd = 0;
  977.     } else if (strcmp(string+3, "out") == 0) {
  978.         fd = 1;
  979.     } else if (strcmp(string+3, "err") == 0) {
  980.         fd = 2;
  981.     } else {
  982.         goto badId;
  983.     }
  984.     } else {
  985.     badId:
  986.     Tcl_AppendResult(interp, "bad file identifier \"", string,
  987.         "\"", (char *) NULL);
  988.     return TCL_ERROR;
  989.     }
  990.  
  991.     if (fd >= iPtr->numFiles) {
  992.     if ((iPtr->numFiles == 0) && (fd <= 2)) {
  993.         TclMakeFileTable(iPtr, fd);
  994.     } else {
  995.         notOpen:
  996.         Tcl_AppendResult(interp, "file \"", string, "\" isn't open",
  997.             (char *) NULL);
  998.         return TCL_ERROR;
  999.     }
  1000.     }
  1001.     if (iPtr->filePtrArray[fd] == NULL) {
  1002.     goto notOpen;
  1003.     }
  1004.     *filePtrPtr = iPtr->filePtrArray[fd];
  1005.     return TCL_OK;
  1006. }
  1007.